home *** CD-ROM | disk | FTP | other *** search
/ Sprite 1984 - 1993 / Sprite 1984 - 1993.iso / src / lib / tk2.3 / dist / tkMessage.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-06-08  |  22.9 KB  |  789 lines

  1. /* 
  2.  * tkMessage.c --
  3.  *
  4.  *    This module implements a message widgets for the Tk
  5.  *    toolkit.  A message widget displays a multi-line string
  6.  *    in a window according to a particular aspect ratio.
  7.  *
  8.  * Copyright 1990 Regents of the University of California.
  9.  * Permission to use, copy, modify, and distribute this
  10.  * software and its documentation for any purpose and without
  11.  * fee is hereby granted, provided that the above copyright
  12.  * notice appear in all copies.  The University of California
  13.  * makes no representations about the suitability of this
  14.  * software for any purpose.  It is provided "as is" without
  15.  * express or implied warranty.
  16.  */
  17.  
  18. #ifndef lint
  19. static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkMessage.c,v 1.36 92/06/08 11:06:05 ouster Exp $ SPRITE (Berkeley)";
  20. #endif
  21.  
  22. #include "tkConfig.h"
  23. #include "default.h"
  24. #include "tkInt.h"
  25.  
  26. /*
  27.  * A data structure of the following type is kept for each message
  28.  * widget managed by this file:
  29.  */
  30.  
  31. typedef struct {
  32.     Tk_Window tkwin;        /* Window that embodies the message.  NULL
  33.                  * means that the window has been destroyed
  34.                  * but the data structures haven't yet been
  35.                  * cleaned up.*/
  36.     Tcl_Interp *interp;        /* Interpreter associated with message. */
  37.     Tk_Uid string;        /* String displayed in message. */
  38.     int numChars;        /* Number of characters in string, not
  39.                  * including terminating NULL character. */
  40.     char *textVarName;        /* Name of variable (malloc'ed) or NULL.
  41.                  * If non-NULL, message displays the contents
  42.                  * of this variable. */
  43.  
  44.     /*
  45.      * Information used when displaying widget:
  46.      */
  47.  
  48.     Tk_3DBorder border;        /* Structure used to draw 3-D border and
  49.                  * background.  NULL means a border hasn't
  50.                  * been created yet. */
  51.     int borderWidth;        /* Width of border. */
  52.     int relief;            /* 3-D effect: TK_RELIEF_RAISED, etc. */
  53.     XFontStruct *fontPtr;    /* Information about text font, or NULL. */
  54.     XColor *fgColorPtr;        /* Foreground color in normal mode. */
  55.     GC textGC;            /* GC for drawing text in normal mode. */
  56.     int padX, padY;        /* User-requested extra space around text. */
  57.     Tk_Anchor anchor;        /* Where to position text within window region
  58.                  * if window is larger or smaller than
  59.                  * needed. */
  60.     int width;            /* User-requested width, in pixels.  0 means
  61.                  * compute width using aspect ratio below. */
  62.     int aspect;            /* Desired aspect ratio for window
  63.                  * (100*width/height). */
  64.     int lineLength;        /* Length of each line, in pixels.  Computed
  65.                  * from width and/or aspect. */
  66.     int msgHeight;        /* Total number of pixels in vertical direction
  67.                  * needed to display message. */
  68.     Tk_Justify justify;        /* Justification for text. */
  69.  
  70.     /*
  71.      * Miscellaneous information:
  72.      */
  73.  
  74.     Cursor cursor;        /* Current cursor for window, or None. */
  75.     int flags;            /* Various flags;  see below for
  76.                  * definitions. */
  77. } Message;
  78.  
  79. /*
  80.  * Flag bits for messages:
  81.  *
  82.  * REDRAW_PENDING:        Non-zero means a DoWhenIdle handler
  83.  *                has already been queued to redraw
  84.  *                this window.
  85.  * CLEAR_NEEDED;        Need to clear the window when redrawing.
  86.  */
  87.  
  88. #define REDRAW_PENDING        1
  89. #define CLEAR_NEEDED        2
  90.  
  91. /*
  92.  * Information used for argv parsing.
  93.  */
  94.  
  95.  
  96. static Tk_ConfigSpec configSpecs[] = {
  97.     {TK_CONFIG_ANCHOR, "-anchor", "anchor", "Anchor",
  98.     DEF_MESSAGE_ANCHOR, Tk_Offset(Message, anchor), 0},
  99.     {TK_CONFIG_INT, "-aspect", "aspect", "Aspect",
  100.     DEF_MESSAGE_ASPECT, Tk_Offset(Message, aspect), 0},
  101.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  102.     DEF_MESSAGE_BG_COLOR, Tk_Offset(Message, border),
  103.     TK_CONFIG_COLOR_ONLY},
  104.     {TK_CONFIG_BORDER, "-background", "background", "Background",
  105.     DEF_MESSAGE_BG_MONO, Tk_Offset(Message, border),
  106.     TK_CONFIG_MONO_ONLY},
  107.     {TK_CONFIG_SYNONYM, "-bd", "borderWidth", (char *) NULL,
  108.     (char *) NULL, 0, 0},
  109.     {TK_CONFIG_SYNONYM, "-bg", "background", (char *) NULL,
  110.     (char *) NULL, 0, 0},
  111.     {TK_CONFIG_PIXELS, "-borderwidth", "borderWidth", "BorderWidth",
  112.     DEF_MESSAGE_BORDER_WIDTH, Tk_Offset(Message, borderWidth), 0},
  113.     {TK_CONFIG_ACTIVE_CURSOR, "-cursor", "cursor", "Cursor",
  114.     DEF_MESSAGE_CURSOR, Tk_Offset(Message, cursor), TK_CONFIG_NULL_OK},
  115.     {TK_CONFIG_SYNONYM, "-fg", "foreground", (char *) NULL,
  116.     (char *) NULL, 0, 0},
  117.     {TK_CONFIG_FONT, "-font", "font", "Font",
  118.     DEF_MESSAGE_FONT, Tk_Offset(Message, fontPtr), 0},
  119.     {TK_CONFIG_COLOR, "-foreground", "foreground", "Foreground",
  120.     DEF_MESSAGE_FG, Tk_Offset(Message, fgColorPtr), 0},
  121.     {TK_CONFIG_JUSTIFY, "-justify", "justify", "Justify",
  122.     DEF_MESSAGE_JUSTIFY, Tk_Offset(Message, justify), 0},
  123.     {TK_CONFIG_PIXELS, "-padx", "padX", "Pad",
  124.     DEF_MESSAGE_PADX, Tk_Offset(Message, padX), 0},
  125.     {TK_CONFIG_PIXELS, "-pady", "padY", "Pad",
  126.     DEF_MESSAGE_PADY, Tk_Offset(Message, padY), 0},
  127.     {TK_CONFIG_RELIEF, "-relief", "relief", "Relief",
  128.     DEF_MESSAGE_RELIEF, Tk_Offset(Message, relief), 0},
  129.     {TK_CONFIG_STRING, "-text", "text", "Text",
  130.     DEF_MESSAGE_TEXT, Tk_Offset(Message, string), 0},
  131.     {TK_CONFIG_STRING, "-textvariable", "textVariable", "Variable",
  132.     DEF_MESSAGE_TEXT_VARIABLE, Tk_Offset(Message, textVarName),
  133.     TK_CONFIG_NULL_OK},
  134.     {TK_CONFIG_PIXELS, "-width", "width", "Width",
  135.     DEF_MESSAGE_WIDTH, Tk_Offset(Message, width), 0},
  136.     {TK_CONFIG_END, (char *) NULL, (char *) NULL, (char *) NULL,
  137.     (char *) NULL, 0, 0}
  138. };
  139.  
  140. /*
  141.  * Forward declarations for procedures defined later in this file:
  142.  */
  143.  
  144. static void        MessageEventProc _ANSI_ARGS_((ClientData clientData,
  145.                 XEvent *eventPtr));
  146. static char *        MessageTextVarProc _ANSI_ARGS_((ClientData clientData,
  147.                 Tcl_Interp *interp, char *name1, char *name2,
  148.                 int flags));
  149. static int        MessageWidgetCmd _ANSI_ARGS_((ClientData clientData,
  150.                 Tcl_Interp *interp, int argc, char **argv));
  151. static void        ComputeMessageGeometry _ANSI_ARGS_((Message *msgPtr));
  152. static int        ConfigureMessage _ANSI_ARGS_((Tcl_Interp *interp,
  153.                 Message *msgPtr, int argc, char **argv,
  154.                 int flags));
  155. static void        DestroyMessage _ANSI_ARGS_((ClientData clientData));
  156. static void        DisplayMessage _ANSI_ARGS_((ClientData clientData));
  157.  
  158. /*
  159.  *--------------------------------------------------------------
  160.  *
  161.  * Tk_MessageCmd --
  162.  *
  163.  *    This procedure is invoked to process the "message" Tcl
  164.  *    command.  See the user documentation for details on what
  165.  *    it does.
  166.  *
  167.  * Results:
  168.  *    A standard Tcl result.
  169.  *
  170.  * Side effects:
  171.  *    See the user documentation.
  172.  *
  173.  *--------------------------------------------------------------
  174.  */
  175.  
  176. int
  177. Tk_MessageCmd(clientData, interp, argc, argv)
  178.     ClientData clientData;    /* Main window associated with
  179.                  * interpreter. */
  180.     Tcl_Interp *interp;        /* Current interpreter. */
  181.     int argc;            /* Number of arguments. */
  182.     char **argv;        /* Argument strings. */
  183. {
  184.     register Message *msgPtr;
  185.     Tk_Window new;
  186.     Tk_Window tkwin = (Tk_Window) clientData;
  187.  
  188.     if (argc < 2) {
  189.     Tcl_AppendResult(interp, "wrong # args:  should be \"",
  190.         argv[0], " pathName ?options?\"", (char *) NULL);
  191.     return TCL_ERROR;
  192.     }
  193.  
  194.     new = Tk_CreateWindowFromPath(interp, tkwin, argv[1], (char *) NULL);
  195.     if (new == NULL) {
  196.     return TCL_ERROR;
  197.     }
  198.  
  199.     msgPtr = (Message *) ckalloc(sizeof(Message));
  200.     msgPtr->tkwin = new;
  201.     msgPtr->interp = interp;
  202.     msgPtr->string = NULL;
  203.     msgPtr->textVarName = NULL;
  204.     msgPtr->border = NULL;
  205.     msgPtr->borderWidth = 0;
  206.     msgPtr->relief = TK_RELIEF_FLAT;
  207.     msgPtr->fontPtr = NULL;
  208.     msgPtr->fgColorPtr = NULL;
  209.     msgPtr->textGC = NULL;
  210.     msgPtr->padX = 0;
  211.     msgPtr->padY = 0;
  212.     msgPtr->width = 0;
  213.     msgPtr->aspect = 150;
  214.     msgPtr->justify = TK_JUSTIFY_LEFT;
  215.     msgPtr->cursor = None;
  216.     msgPtr->flags = 0;
  217.  
  218.     Tk_SetClass(msgPtr->tkwin, "Message");
  219.     Tk_CreateEventHandler(msgPtr->tkwin, ExposureMask|StructureNotifyMask,
  220.         MessageEventProc, (ClientData) msgPtr);
  221.     Tcl_CreateCommand(interp, Tk_PathName(msgPtr->tkwin), MessageWidgetCmd,
  222.         (ClientData) msgPtr, (void (*)()) NULL);
  223.     if (ConfigureMessage(interp, msgPtr, argc-2, argv+2, 0) != TCL_OK) {
  224.     goto error;
  225.     }
  226.  
  227.     interp->result = Tk_PathName(msgPtr->tkwin);
  228.     return TCL_OK;
  229.  
  230.     error:
  231.     Tk_DestroyWindow(msgPtr->tkwin);
  232.     return TCL_ERROR;
  233. }
  234.  
  235. /*
  236.  *--------------------------------------------------------------
  237.  *
  238.  * MessageWidgetCmd --
  239.  *
  240.  *    This procedure is invoked to process the Tcl command
  241.  *    that corresponds to a widget managed by this module.
  242.  *    See the user documentation for details on what it does.
  243.  *
  244.  * Results:
  245.  *    A standard Tcl result.
  246.  *
  247.  * Side effects:
  248.  *    See the user documentation.
  249.  *
  250.  *--------------------------------------------------------------
  251.  */
  252.  
  253. static int
  254. MessageWidgetCmd(clientData, interp, argc, argv)
  255.     ClientData clientData;    /* Information about message widget. */
  256.     Tcl_Interp *interp;        /* Current interpreter. */
  257.     int argc;            /* Number of arguments. */
  258.     char **argv;        /* Argument strings. */
  259. {
  260.     register Message *msgPtr = (Message *) clientData;
  261.     int length;
  262.     char c;
  263.  
  264.     if (argc < 2) {
  265.     Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
  266.         " option ?arg arg ...?\"", (char *) NULL);
  267.     return TCL_ERROR;
  268.     }
  269.     c = argv[1][0];
  270.     length = strlen(argv[1]);
  271.     if ((c == 'c') && (strncmp(argv[1], "configure", length) == 0)) {
  272.     if (argc == 2) {
  273.         return Tk_ConfigureInfo(interp, msgPtr->tkwin, configSpecs,
  274.             (char *) msgPtr, (char *) NULL, 0);
  275.     } else if (argc == 3) {
  276.         return Tk_ConfigureInfo(interp, msgPtr->tkwin, configSpecs,
  277.             (char *) msgPtr, argv[2], 0);
  278.     } else {
  279.         return ConfigureMessage(interp, msgPtr, argc-2, argv+2,
  280.             TK_CONFIG_ARGV_ONLY);
  281.     }
  282.     } else {
  283.     Tcl_AppendResult(interp, "bad option \"", argv[1],
  284.         "\":  must be configure", (char *) NULL);
  285.     return TCL_ERROR;
  286.     }
  287. }
  288.  
  289. /*
  290.  *----------------------------------------------------------------------
  291.  *
  292.  * DestroyMessage --
  293.  *
  294.  *    This procedure is invoked by Tk_EventuallyFree or Tk_Release
  295.  *    to clean up the internal structure of a message at a safe time
  296.  *    (when no-one is using it anymore).
  297.  *
  298.  * Results:
  299.  *    None.
  300.  *
  301.  * Side effects:
  302.  *    Everything associated with the message is freed up.
  303.  *
  304.  *----------------------------------------------------------------------
  305.  */
  306.  
  307. static void
  308. DestroyMessage(clientData)
  309.     ClientData clientData;    /* Info about message widget. */
  310. {
  311.     register Message *msgPtr = (Message *) clientData;
  312.  
  313.     if (msgPtr->string != NULL) {
  314.     ckfree(msgPtr->string);
  315.     }
  316.     if (msgPtr->textVarName != NULL) {
  317.     Tcl_UntraceVar(msgPtr->interp, msgPtr->textVarName,
  318.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  319.         MessageTextVarProc, (ClientData) msgPtr);
  320.     ckfree(msgPtr->textVarName);
  321.     }
  322.     if (msgPtr->border != NULL) {
  323.     Tk_Free3DBorder(msgPtr->border);
  324.     }
  325.     if (msgPtr->fontPtr != NULL) {
  326.     Tk_FreeFontStruct(msgPtr->fontPtr);
  327.     }
  328.     if (msgPtr->fgColorPtr != NULL) {
  329.     Tk_FreeColor(msgPtr->fgColorPtr);
  330.     }
  331.     if (msgPtr->textGC != None) {
  332.     Tk_FreeGC(msgPtr->textGC);
  333.     }
  334.     if (msgPtr->cursor != None) {
  335.     Tk_FreeCursor(msgPtr->cursor);
  336.     }
  337.     ckfree((char *) msgPtr);
  338. }
  339.  
  340. /*
  341.  *----------------------------------------------------------------------
  342.  *
  343.  * ConfigureMessage --
  344.  *
  345.  *    This procedure is called to process an argv/argc list, plus
  346.  *    the Tk option database, in order to configure (or
  347.  *    reconfigure) a message widget.
  348.  *
  349.  * Results:
  350.  *    The return value is a standard Tcl result.  If TCL_ERROR is
  351.  *    returned, then interp->result contains an error message.
  352.  *
  353.  * Side effects:
  354.  *    Configuration information, such as text string, colors, font,
  355.  *    etc. get set for msgPtr;  old resources get freed, if there
  356.  *    were any.
  357.  *
  358.  *----------------------------------------------------------------------
  359.  */
  360.  
  361. static int
  362. ConfigureMessage(interp, msgPtr, argc, argv, flags)
  363.     Tcl_Interp *interp;        /* Used for error reporting. */
  364.     register Message *msgPtr;    /* Information about widget;  may or may
  365.                  * not already have values for some fields. */
  366.     int argc;            /* Number of valid entries in argv. */
  367.     char **argv;        /* Arguments. */
  368.     int flags;            /* Flags to pass to Tk_ConfigureWidget. */
  369. {
  370.     XGCValues gcValues;
  371.     GC newGC;
  372.  
  373.     /*
  374.      * Eliminate any existing trace on a variable monitored by the message.
  375.      */
  376.  
  377.     if (msgPtr->textVarName != NULL) {
  378.     Tcl_UntraceVar(interp, msgPtr->textVarName, 
  379.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  380.         MessageTextVarProc, (ClientData) msgPtr);
  381.     }
  382.  
  383.     if (Tk_ConfigureWidget(interp, msgPtr->tkwin, configSpecs,
  384.         argc, argv, (char *) msgPtr, flags) != TCL_OK) {
  385.     return TCL_ERROR;
  386.     }
  387.  
  388.     /*
  389.      * If the message is to display the value of a variable, then set up
  390.      * a trace on the variable's value, create the variable if it doesn't
  391.      * exist, and fetch its current value.
  392.      */
  393.  
  394.     if (msgPtr->textVarName != NULL) {
  395.     char *value;
  396.  
  397.     value = Tcl_GetVar(interp, msgPtr->textVarName, TCL_GLOBAL_ONLY);
  398.     if (value == NULL) {
  399.         Tcl_SetVar(interp, msgPtr->textVarName, msgPtr->string,
  400.             TCL_GLOBAL_ONLY);
  401.     } else {
  402.         if (msgPtr->string != NULL) {
  403.         ckfree(msgPtr->string);
  404.         }
  405.         msgPtr->string = ckalloc((unsigned) (strlen(value) + 1));
  406.         strcpy(msgPtr->string, value);
  407.     }
  408.     Tcl_TraceVar(interp, msgPtr->textVarName,
  409.         TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  410.         MessageTextVarProc, (ClientData) msgPtr);
  411.     }
  412.  
  413.     /*
  414.      * A few other options need special processing, such as setting
  415.      * the background from a 3-D border or handling special defaults
  416.      * that couldn't be specified to Tk_ConfigureWidget.
  417.      */
  418.  
  419.     msgPtr->numChars = strlen(msgPtr->string);
  420.  
  421.     Tk_SetBackgroundFromBorder(msgPtr->tkwin, msgPtr->border);
  422.  
  423.     gcValues.font = msgPtr->fontPtr->fid;
  424.     gcValues.foreground = msgPtr->fgColorPtr->pixel;
  425.     newGC = Tk_GetGC(msgPtr->tkwin, GCForeground|GCFont,
  426.         &gcValues);
  427.     if (msgPtr->textGC != None) {
  428.     Tk_FreeGC(msgPtr->textGC);
  429.     }
  430.     msgPtr->textGC = newGC;
  431.  
  432.     if (msgPtr->padX == -1) {
  433.     msgPtr->padX = msgPtr->fontPtr->ascent/2;
  434.     }
  435.  
  436.     if (msgPtr->padY == -1) {
  437.     msgPtr->padY = msgPtr->fontPtr->ascent/4;
  438.     }
  439.  
  440.     if (msgPtr->justify == TK_JUSTIFY_FILL) {
  441.     interp->result = "can't use \"fill\" justify style in messages";
  442.     return TCL_ERROR;
  443.     }
  444.  
  445.     /*
  446.      * Recompute the desired geometry for the window, and arrange for
  447.      * the window to be redisplayed.
  448.      */
  449.  
  450.     ComputeMessageGeometry(msgPtr);
  451.     if ((msgPtr->tkwin != NULL) && Tk_IsMapped(msgPtr->tkwin)
  452.         && !(msgPtr->flags & REDRAW_PENDING)) {
  453.     Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
  454.     msgPtr->flags |= REDRAW_PENDING|CLEAR_NEEDED;
  455.     }
  456.  
  457.     return TCL_OK;
  458. }
  459.  
  460. /*
  461.  *--------------------------------------------------------------
  462.  *
  463.  * ComputeMessageGeometry --
  464.  *
  465.  *    Compute the desired geometry for a message window,
  466.  *    taking into account the desired aspect ratio for the
  467.  *    window.
  468.  *
  469.  * Results:
  470.  *    None.
  471.  *
  472.  * Side effects:
  473.  *    Tk_GeometryRequest is called to inform the geometry
  474.  *    manager of the desired geometry for this window.
  475.  *
  476.  *--------------------------------------------------------------
  477.  */
  478.  
  479. static void
  480. ComputeMessageGeometry(msgPtr)
  481.     register Message *msgPtr;    /* Information about window. */
  482. {
  483.     char *p;
  484.     int width, inc, height, numLines;
  485.     int thisWidth, maxWidth;
  486.     int aspect, lowerBound, upperBound;
  487.  
  488.     /*
  489.      * Compute acceptable bounds for the final aspect ratio.
  490.      */
  491.     aspect = msgPtr->aspect/10;
  492.     if (aspect < 5) {
  493.     aspect = 5;
  494.     }
  495.     lowerBound = msgPtr->aspect - aspect;
  496.     upperBound = msgPtr->aspect + aspect;
  497.  
  498.     /*
  499.      * Do the computation in multiple passes:  start off with
  500.      * a very wide window, and compute its height.  Then change
  501.      * the width and try again.  Reduce the size of the change
  502.      * and iterate until dimensions are found that approximate
  503.      * the desired aspect ratio.  Or, if the user gave an explicit
  504.      * width then just use that.
  505.      */
  506.  
  507.     if (msgPtr->width > 0) {
  508.     width = msgPtr->width;
  509.     inc = 0;
  510.     } else {
  511.     width = WidthOfScreen(Tk_Screen(msgPtr->tkwin))/2;
  512.     inc = width/2;
  513.     }
  514.     for ( ; ; inc /= 2) {
  515.     maxWidth = 0;
  516.     for (numLines = 1, p = msgPtr->string; ; numLines++)  {
  517.         if (*p == '\n') {
  518.         p++;
  519.         continue;
  520.         }
  521.         p += TkMeasureChars(msgPtr->fontPtr, p,
  522.             msgPtr->numChars - (p - msgPtr->string), 0, width,
  523.             TK_WHOLE_WORDS|TK_AT_LEAST_ONE, &thisWidth);
  524.         if (thisWidth > maxWidth) {
  525.         maxWidth = thisWidth;
  526.         }
  527.         if (*p == 0) {
  528.         break;
  529.         }
  530.  
  531.         /*
  532.          * Skip spaces and tabs at the beginning of a line, unless
  533.          * they follow a user-requested newline.
  534.          */
  535.  
  536.         while (isspace(*p)) {
  537.         if (*p == '\n') {
  538.             p++;
  539.             break;
  540.         }
  541.         p++;
  542.         }
  543.     }
  544.  
  545.     height = numLines * (msgPtr->fontPtr->ascent
  546.         + msgPtr->fontPtr->descent) + 2*msgPtr->borderWidth
  547.         + 2*msgPtr->padY;
  548.     if (inc <= 2) {
  549.         break;
  550.     }
  551.     aspect = (100*(maxWidth + 2*msgPtr->borderWidth
  552.         + 2*msgPtr->padX))/height;
  553.     if (aspect < lowerBound) {
  554.         width += inc;
  555.     } else if (aspect > upperBound) {
  556.         width -= inc;
  557.     } else {
  558.         break;
  559.     }
  560.     }
  561.     msgPtr->lineLength = maxWidth;
  562.     msgPtr->msgHeight = numLines * (msgPtr->fontPtr->ascent
  563.         + msgPtr->fontPtr->descent);
  564.     Tk_GeometryRequest(msgPtr->tkwin,
  565.         maxWidth + 2*msgPtr->borderWidth + 2*msgPtr->padX, height);
  566.     Tk_SetInternalBorder(msgPtr->tkwin, msgPtr->borderWidth);
  567. }
  568.  
  569. /*
  570.  *--------------------------------------------------------------
  571.  *
  572.  * DisplayMessage --
  573.  *
  574.  *    This procedure redraws the contents of a message window.
  575.  *
  576.  * Results:
  577.  *    None.
  578.  *
  579.  * Side effects:
  580.  *    Information appears on the screen.
  581.  *
  582.  *--------------------------------------------------------------
  583.  */
  584.  
  585. static void
  586. DisplayMessage(clientData)
  587.     ClientData clientData;    /* Information about window. */
  588. {
  589.     register Message *msgPtr = (Message *) clientData;
  590.     register Tk_Window tkwin = msgPtr->tkwin;
  591.     char *p;
  592.     int x, y, lineLength, numChars, charsLeft;
  593.  
  594.     msgPtr->flags &= ~REDRAW_PENDING;
  595.     if ((msgPtr->tkwin == NULL) || !Tk_IsMapped(tkwin)) {
  596.     return;
  597.     }
  598.     if (msgPtr->flags & CLEAR_NEEDED) {
  599.     XClearWindow(Tk_Display(tkwin), Tk_WindowId(tkwin));
  600.     msgPtr->flags &= ~CLEAR_NEEDED;
  601.     }
  602.  
  603.     /*
  604.      * Compute starting y-location for message based on message size
  605.      * and anchor option.
  606.      */
  607.  
  608.     switch (msgPtr->anchor) {
  609.     case TK_ANCHOR_NW: case TK_ANCHOR_N: case TK_ANCHOR_NE:
  610.         y = msgPtr->borderWidth + msgPtr->padY;
  611.         break;
  612.     case TK_ANCHOR_W: case TK_ANCHOR_CENTER: case TK_ANCHOR_E:
  613.         y = (Tk_Height(tkwin) - msgPtr->msgHeight)/2;
  614.         break;
  615.     default:
  616.         y = Tk_Height(tkwin) - msgPtr->borderWidth - msgPtr->padY
  617.             - msgPtr->msgHeight;
  618.         break;
  619.     }
  620.     y += msgPtr->fontPtr->ascent;
  621.  
  622.     /*
  623.      * Work through the string to display one line at a time.
  624.      * Display each line in three steps.  First compute the
  625.      * line's width, then figure out where to display the
  626.      * line to justify it properly, then display the line.
  627.      */
  628.  
  629.     for (p = msgPtr->string, charsLeft = msgPtr->numChars; *p != 0;
  630.         y += msgPtr->fontPtr->ascent + msgPtr->fontPtr->descent) {
  631.     if (*p == '\n') {
  632.         p++;
  633.         charsLeft--;
  634.         continue;
  635.     }
  636.     numChars = TkMeasureChars(msgPtr->fontPtr, p, charsLeft, 0,
  637.         msgPtr->lineLength, TK_WHOLE_WORDS|TK_AT_LEAST_ONE,
  638.         &lineLength);
  639.     switch (msgPtr->anchor) {
  640.         case TK_ANCHOR_NW: case TK_ANCHOR_W: case TK_ANCHOR_SW:
  641.         x = msgPtr->borderWidth + msgPtr->padX;
  642.         break;
  643.         case TK_ANCHOR_N: case TK_ANCHOR_CENTER: case TK_ANCHOR_S:
  644.         x = (Tk_Width(tkwin) - msgPtr->lineLength)/2;
  645.         break;
  646.         default:
  647.         x = Tk_Width(tkwin) - msgPtr->borderWidth - msgPtr->padX
  648.             - msgPtr->lineLength;
  649.         break;
  650.     }
  651.     if (msgPtr->justify == TK_JUSTIFY_CENTER) {
  652.         x += (msgPtr->lineLength - lineLength)/2;
  653.     } else if (msgPtr->justify == TK_JUSTIFY_RIGHT) {
  654.         x += msgPtr->lineLength - lineLength;
  655.     }
  656.     TkDisplayChars(Tk_Display(tkwin), Tk_WindowId(tkwin),
  657.         msgPtr->textGC, msgPtr->fontPtr, p, numChars, x, y, 0);
  658.     p += numChars;
  659.     charsLeft -= numChars;
  660.  
  661.     /*
  662.      * Skip blanks at the beginning of a line, unless they follow
  663.      * a user-requested newline.
  664.      */
  665.  
  666.     while (isspace(*p)) {
  667.         charsLeft--;
  668.         if (*p == '\n') {
  669.         p++;
  670.         break;
  671.         }
  672.         p++;
  673.     }
  674.     }
  675.  
  676.     if (msgPtr->relief != TK_RELIEF_FLAT) {
  677.     Tk_Draw3DRectangle(Tk_Display(tkwin), Tk_WindowId(tkwin),
  678.         msgPtr->border, 0, 0, Tk_Width(tkwin), Tk_Height(tkwin),
  679.         msgPtr->borderWidth, msgPtr->relief);
  680.     }
  681. }
  682.  
  683. /*
  684.  *--------------------------------------------------------------
  685.  *
  686.  * MessageEventProc --
  687.  *
  688.  *    This procedure is invoked by the Tk dispatcher for various
  689.  *    events on messages.
  690.  *
  691.  * Results:
  692.  *    None.
  693.  *
  694.  * Side effects:
  695.  *    When the window gets deleted, internal structures get
  696.  *    cleaned up.  When it gets exposed, it is redisplayed.
  697.  *
  698.  *--------------------------------------------------------------
  699.  */
  700.  
  701. static void
  702. MessageEventProc(clientData, eventPtr)
  703.     ClientData clientData;    /* Information about window. */
  704.     XEvent *eventPtr;        /* Information about event. */
  705. {
  706.     Message *msgPtr = (Message *) clientData;
  707.  
  708.     if ((eventPtr->type == Expose) && (eventPtr->xexpose.count == 0)) {
  709.     if ((msgPtr->tkwin != NULL) && !(msgPtr->flags & REDRAW_PENDING)) {
  710.         Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
  711.         msgPtr->flags |= REDRAW_PENDING;
  712.     }
  713.     } else if (eventPtr->type == DestroyNotify) {
  714.     Tcl_DeleteCommand(msgPtr->interp, Tk_PathName(msgPtr->tkwin));
  715.     msgPtr->tkwin = NULL;
  716.     if (msgPtr->flags & REDRAW_PENDING) {
  717.         Tk_CancelIdleCall(DisplayMessage, (ClientData) msgPtr);
  718.     }
  719.     Tk_EventuallyFree((ClientData) msgPtr, DestroyMessage);
  720.     }
  721. }
  722.  
  723. /*
  724.  *--------------------------------------------------------------
  725.  *
  726.  * MessageTextVarProc --
  727.  *
  728.  *    This procedure is invoked when someone changes the variable
  729.  *    whose contents are to be displayed in a message.
  730.  *
  731.  * Results:
  732.  *    NULL is always returned.
  733.  *
  734.  * Side effects:
  735.  *    The text displayed in the message will change to match the
  736.  *    variable.
  737.  *
  738.  *--------------------------------------------------------------
  739.  */
  740.  
  741.     /* ARGSUSED */
  742. static char *
  743. MessageTextVarProc(clientData, interp, name1, name2, flags)
  744.     ClientData clientData;    /* Information about message. */
  745.     Tcl_Interp *interp;        /* Interpreter containing variable. */
  746.     char *name1;        /* Name of variable. */
  747.     char *name2;        /* Second part of variable name. */
  748.     int flags;            /* Information about what happened. */
  749. {
  750.     register Message *msgPtr = (Message *) clientData;
  751.     char *value;
  752.  
  753.     /*
  754.      * If the variable is unset, then immediately recreate it unless
  755.      * the whole interpreter is going away.
  756.      */
  757.  
  758.     if (flags & TCL_TRACE_UNSETS) {
  759.     if ((flags & TCL_TRACE_DESTROYED) && !(flags & TCL_INTERP_DESTROYED)) {
  760.         Tcl_SetVar2(interp, name1, name2, msgPtr->string,
  761.             flags & TCL_GLOBAL_ONLY);
  762.         Tcl_TraceVar2(interp, name1, name2,
  763.             TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
  764.             MessageTextVarProc, clientData);
  765.     }
  766.     return (char *) NULL;
  767.     }
  768.  
  769.     value = Tcl_GetVar2(interp, name1, name2, flags & TCL_GLOBAL_ONLY);
  770.     if (value == NULL) {
  771.     value = "";
  772.     }
  773.     if (msgPtr->string != NULL) {
  774.     ckfree(msgPtr->string);
  775.     }
  776.     msgPtr->numChars = strlen(value);
  777.     msgPtr->string = ckalloc((unsigned) (msgPtr->numChars + 1));
  778.     strcpy(msgPtr->string, value);
  779.     ComputeMessageGeometry(msgPtr);
  780.  
  781.     msgPtr->flags |= CLEAR_NEEDED;
  782.     if ((msgPtr->tkwin != NULL) && Tk_IsMapped(msgPtr->tkwin)
  783.         && !(msgPtr->flags & REDRAW_PENDING)) {
  784.     Tk_DoWhenIdle(DisplayMessage, (ClientData) msgPtr);
  785.     msgPtr->flags |= REDRAW_PENDING;
  786.     }
  787.     return (char *) NULL;
  788. }
  789.